package com.sky.service.impl;

import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.sky.constant.MessageConstant;
import com.sky.constant.StatusConstant;
import com.sky.dto.DishDTO;
import com.sky.dto.DishPageQueryDTO;
import com.sky.entity.Dish;
import com.sky.entity.DishFlavor;
import com.sky.entity.Setmeal;
import com.sky.exception.DeletionNotAllowedException;
import com.sky.mapper.DishFlavorMapper;
import com.sky.mapper.DishMapper;
import com.sky.mapper.SetmealDishMapper;
import com.sky.mapper.SetmealMapper;
import com.sky.result.PageResult;
import com.sky.service.DishService;
import com.sky.vo.DishVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * @author dgz
 */
@Service    //这里为啥加servic注解，而不是在service接口加？因为接口不能被实例化，所以Springmvc规定@Service一般是加在实现类上的，而不是接口上
@Slf4j
public class DishServiceImpl implements DishService {

    @Autowired
    private DishMapper dishMapper;
    @Autowired
    private DishFlavorMapper dishFlavorMapper;
    @Autowired
    private SetmealDishMapper setmealDishMapper;
    @Autowired
    private SetmealMapper setmealMapper;

    /**
     * 新增菜品和对应的口味
     * @param dishDTO
     */
    /**这里涉及到两个表操作，即往菜品表插入数据，如果涉及到口味还要往口味表插入数据，为了保证数据一致性，所以这里还需要事务的注解，这样就保证这整个方法是一个原子性，要么全成功，要么全失败*/
    @Transactional  /**这个注解，要开启这种注解方式的事务控制，其实在我们的启动类上通过注解已经开启过了*/
    public void saveWithFlavor(DishDTO dishDTO) {

        Dish dish = new Dish();

        /**dish对象的属性是空的，相关属性需要赋值，而DishDTO已经给我们传过来了，那么就可以通过对象的属性拷贝传过来
         * 这里要注意，dish和dishDTo属性名要保持一致，这里已经调整好一致了*/
        BeanUtils.copyProperties(dishDTO,dish);

        //1.向菜品表插入1条数据（前端的表单只插入一条）
        /**参数不传DishDTO，因为DishDTO包含了口味数据，这里只是对菜品插入数据，所以这里传一个实体对象进去就可以*/
        dishMapper.insert(dish);

        //获取insert生成的主键值
        /**3.完成2中if的动态批量插入后，前端能传dishId这一项过来吗？
         * 不能，因为在新增菜品时还没添加完，这个dishId还没有值，接口文档中dishId是非必须的，前端没有办法给我们传过来，只能传接口文档必须的name和value，即口味名称和口味值
         * 那怎么传dishId？上面dishMapper.insert(dish);保存了dishId，因此需要通过geiId获取,但直接获取是获取不到的，需要执行完DishMapper.xml产生主键值返回才会获取得到，在xml加useG...Keys，表使用返回id，key..ty表赋值给对象id
         * 这时才能获取id值*/
        Long dishId = dish.getId();

        //2.向口味表插入n条数据（口味可能有多个）
        /**口味数据在dto里是通过一个集合方式来封装的，所以说先把这个集合数据给它取出来*/
        List<DishFlavor> flavors = dishDTO.getFlavors();
        /**判断这个口味集合里有没有数据，因为接口文档中，这个不是必须的，也就是说用户有可能没有提交这个口味数据*/
        if (flavors != null && flavors.size() > 0) {//没写size() > 0有可能出bug
            //4.
            flavors.forEach(dishFlavor -> {
                dishFlavor.setDishId(dishId);
            });
            //向口味表插入n条数据
            /**这时候需要新的mapper，用来操作口味表的mapper,DishFlavorMapper*/
            /**flavors是一个集合，传统方式，需要一个一个遍历然后再一个一个数据插入这口味。
             * 但我们并不需要，我们可以批量插入，也就是说直接把这个集合对象flavors传进去，SQL是支持批量插入的,所以我们可以创建批量方法，然后在mapper.xml中批量插入*/
            dishFlavorMapper.insertBatch(flavors);     /**insertBatch翻译为批量插入，注意service接口也要添加这个方法（建议alt+回车自动生成*/

        }
    }

    /**
     * 菜品分页查询
     * @param dishPageQueryDTO
     * @return
     */
    public PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO) {
        //PageHelper是Mybatis提供了分页插件
        PageHelper.startPage(dishPageQueryDTO.getPage(),dishPageQueryDTO.getPageSize());
        //为适应接口，泛型是DishVO
        Page<DishVO> page = dishMapper.pageQuery(dishPageQueryDTO);
        return new PageResult(page.getTotal(),page.getResult());
    }

    /**
     * 菜品批量删除
     * @param ids
     */
    @Transactional      //涉及多个数据表操作，需要加一个事务注解，并开启事务控制（在主启动中已经开启，所以这里不用开启），保证数据一致性，要么一起成功，要么一起失败
    public void deleteBatch(List<Long> ids) {
        //判断当前菜品是否能够删除---是否存在起售中的菜品？？
        /**传进来的是ids，需要遍历一下*/
        for (Long id : ids) {
            Dish dish = dishMapper.getById(id);
            //status 1，表起售
            if (dish.getStatus() == StatusConstant.ENABLE){
                //当前菜品处于起售中，不能删除
                /**不能删除直接往外抛一个异常就可以，DeletionNotAllowedException是不允许删除异常，异常括号还要传一个message进去，其实就是一个提示信息，这提示信息给给前端展示,这个message已经定义好了一个常量*/
                throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);
            }
        }

        ///判断当前菜品是否能够删除---是否被套餐关联了？？
        /**查setmeal_dish表，需要一个新的mapper*/
        List<Long> setmealIds = setmealDishMapper.getSetmealIdsByDishIds(ids);
        if (setmealIds != null && setmealIds.size() > 0) {
            //当前菜品被套餐关联了，不能删除
            throw new DeletionNotAllowedException(MessageConstant.DISH_BE_RELATED_BY_SETMEAL);
        }

        //删除菜品表中的菜品数据（直接根据主键来删除）
        //dishMapper.deleteById(),因为传进来的ids是个集合，所以需要遍历来删
        /*for (Long id : ids) {
            dishMapper.deleteById(id);//参数是把dish的id传进去
            //删除菜品关联的口味数据
            /** 这个菜品有没有关联的口味呢？如果有的话是不是要给它删除掉，那是不是需要查一下口味有没有再删除。实际上这个地方也没有必要去查，也就是不管你有还是没有，直接尝试去删就完了，
             * 所以这个地方不用再查，直接进for里面
             * 这里直接发一个SQL来删口味表，根据菜品id来删，也就是根据菜品id删除口味
            dishFlavorMapper.deleteByDishId(id);//括号的dishId，就是我们遍历出来的这个id
        }*/

        //批量删除优化
        //根据菜品id集合批量删除菜品数据
        //sql：delete from dish where id in (?,?,?)      （批量删除用in）
        dishMapper.deleteByIds(ids);    //因为是批量删除，所以传ids，而不是id

        //根据菜品id集合批量删除关联的口味数据
        //sql：delete from dish_flavor where dish_id in (?,?,?)      （批量删除用in）
        dishFlavorMapper.deleteByDishIds(ids);
    }

    /**
     * 根据id查询菜品和对应的口味数据
     * @param id
     * @return
     */
    //这个方法，因为涉及到两张表，所以可以分两部查询数据，然后再封装到DishVO中
    public DishVO getByIdWithFlavor(Long id) {
        //根据id查询菜品数据
        Dish dish = dishMapper.getById(id);

        //根据菜品id查询口味数据
        List<DishFlavor> dishFlavors = dishFlavorMapper.getByDishId(id);   //一个菜品可能对应多个口味，所以这里返回的是list集合，里面装的是DishFlavor实体

        //将查询到的数据封装到VO
        DishVO dishVO = new DishVO();
        BeanUtils.copyProperties(dish,dishVO);      //通过对象拷贝BeanUtils，拷贝到dishVo中    dishVO属性还剩下categoryName和flavor没有值，这里是涉及修改的查询，categoryName没有也没关系
        dishVO.setFlavors(dishFlavors);

        return dishVO;
    }

    /**
     * 根据id修改菜品基本信息和对应的口味信息
     * @param dishDTO
     */
    /**这里修改涉及两张表，一个是菜品，一个是关联的口味表，其中口味比较复杂，因为这个方法的口味，会涉及到口味的删除、添加，比较麻烦；
     * 这方法口味有一种简单的处理方式，可以先把当前这个菜品原先关联的口味数据全部都统一删除，然后我再按照你当前传过来的这个口味，也就是dishVo中的flavors，然后再按照新传过的这个口味重新再来插入一边这个数据，这样就可以了
     * 从业务层来说，这是一个修改操作，从技术来说，就是先删除再重新插入
     * 总结：口味处理，先删除原先口味数据，然后再插入新数据，最终就是一个修改的一个效果*/
    @Transactional
    public void updateWithFlavor(DishDTO dishDTO) {
        Dish dish = new Dish();
        BeanUtils.copyProperties(dishDTO,dish);

        //修改菜品表基本信息
        //dishMapper.update(dishDTO)    参数不合适放dishDTO,因为dishDTO有包括口味数据flavors，而当前只是修改菜品基本信息，所以这里传一个菜品Dish对象更合适，所以需要new菜品对象，并把传过来的dishDTO菜品数据拷贝到Dish中
        dishMapper.update(dish);

        //删除原有的口味数据     根据菜品id作为条件来删除，菜品id在dishDTO里有
        dishFlavorMapper.deleteByDishId(dishDTO.getId());

        //重新插入口味数据
        List<DishFlavor> flavors = dishDTO.getFlavors();
        /**然后批量插入口味，批量插入在前面新增时已经做过了，在savaWithFlavor的insertBatch
         * 这里也最好先判断一下这个菜品关联的口味是否有值，然后再批量插入
         * 批量插入前还需要设置dishId，这里的修改也要设置，因为我们的口味数据有可能时新增出来的，这时候dishId时没有传过来的，所以再批量插入之前也需要设置dishId，这里dishId是dishDTO里面的*/
        if (flavors != null && flavors.size() > 0) {
            flavors.forEach(dishFlavor -> {
                dishFlavor.setDishId(dishDTO.getId());
            });
            //向口味表插入n条数据
            dishFlavorMapper.insertBatch(flavors);
        }
    }

    /**
     * 菜品起售停售
     *
     * @param status
     * @param id
     */
    @Transactional
    public void startOrStop(Integer status, Long id) {
        Dish dish = Dish.builder()
                .id(id)
                .status(status)
                .build();
        dishMapper.update(dish);

        if (status == StatusConstant.DISABLE) {
            // 如果是停售操作，还需要将包含当前菜品的套餐也停售
            List<Long> dishIds = new ArrayList<>();
            dishIds.add(id);
            // select setmeal_id from setmeal_dish where dish_id in (?,?,?)
            List<Long> setmealIds = setmealDishMapper.getSetmealIdsByDishIds(dishIds);
            if (setmealIds != null && setmealIds.size() > 0) {
                for (Long setmealId : setmealIds) {
                    Setmeal setmeal = Setmeal.builder()
                            .id(setmealId)
                            .status(StatusConstant.DISABLE)
                            .build();
                    setmealMapper.update(setmeal);
                }
            }
        }
    }

    /**
     * 根据分类id查询菜品
     * @param categoryId
     * @return
     */
    public List<Dish> list(Long categoryId) {
        Dish dish = Dish.builder()
                    .categoryId(categoryId)
                    .status(StatusConstant.ENABLE)
                    .build();
        return dishMapper.list(dish);
    }

    /**
     * 条件查询菜品和口味
     * @param dish
     * @return
     */
    public List<DishVO> listWithFlavor(Dish dish) {
        List<Dish> dishList = dishMapper.list(dish);

        List<DishVO> dishVOList = new ArrayList<>();

        for (Dish d : dishList) {
            DishVO dishVO = new DishVO();
            BeanUtils.copyProperties(d,dishVO);

            //根据菜品id查询对应的口味
            List<DishFlavor> flavors = dishFlavorMapper.getByDishId(d.getId());

            dishVO.setFlavors(flavors);
            dishVOList.add(dishVO);
        }

        return dishVOList;
    }

}
